<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="refresh" content="2000">
<title>红楼梦人物关系图</title>
<link rel="stylesheet/less" type="text/css" href="graph.less">
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/less.js/2.7.3/less.min.js"></script>
<script>
less.pageLoadFinished.then(()=>{onload();});
let dom_graph = null;
let dom_psn_tpl = null;
let dom_cpl_tpl = null;
let vl_tpl = null;
let hl_tpl = null;
let psn_map = null;
let ks_map = null;
function onload() {
    $.ajax({
        url:'graph.json', 
        cache:false, 
        dataType:'json',
        mimeType:'application/json',
        async: true,
        success:function(data){
            dom_graph = $('#graph');
            dom_psn_tpl = $('#template > .person');
            dom_psn_tpl.outlineOffset = parseInt(dom_psn_tpl.css('marginLeft'))-parseInt(dom_psn_tpl.css('outlineOffset'))-parseInt(dom_psn_tpl.css('outlineWidth'));
            vl_tpl = $('#template > .vline');
            hl_tpl = $('#template > .hline');
            dom_cpl_tpl = $('#template > .couple-box');
            $('#gragh-load').remove();
            drawGraph(data);
        },
        error:function(){
            alert('加载图谱数据出错');
        }
    });
}
function drawGraph(data) {
    psn_map = data.person_map;
    ks_map = data.kinship_map;
    for(let family of data['list']) {
        family['name'] = family['family']+'@'+family['branch']
        drawOneFamily(family);
    }
}
function drawOneFamily(family) { // 绘制一个家族
    let master_name_list = createDomForAllMaster(family);
    adjustPositionForAllMaster(family, master_name_list);
    drawLevelLines(family, master_name_list);
    drawCoupleBoxes(family, master_name_list);
    drawFamilyBorder(family);
}
function createDomForAllMaster(family) { // 将主子类型的人物添加到页面，返回所有master的列表
    let master_name_list = [];
    for (let lvl of family['level_list']) {
        let lvl_number = lvl.level_number;
        if (lvl_number<1)
            continue;
        let dom_lvl = dom_graph.find(`[name=level-${lvl_number}]`);
        for (let name of lvl.person_list) {
            let psn = psn_map[name];
            master_name_list.push(name);
            let dom_psn = dom_psn_tpl.clone().attr('person', name);
            dom_psn.find('.name').text(name);
            if (name.length>=5)
                dom_psn.find('.name').addClass(`char${name.length}`)
            calChildrenOrder(psn);
            fillMasterScript(dom_psn, psn);
            fillMasterServant(dom_psn, psn);
            if (psn.info.dead)
                dom_psn.addClass('dead');
            // 将此人dom添加到页面。同时为了将来可以调整位置，在其前面也加入个空白dom
            let dom_space = $('<div class=space-between-person></div>');
            dom_lvl.append(dom_space);
            dom_lvl.append(dom_psn);
            // 将dom记录到psn_map中
            if (family.name==psn.info.family+'@'+psn.info.branch) {
                psn.dom_psn = dom_psn;
                psn.dom_psn_space = dom_space;
            } else if (family.name!=psn.info.family+'@'+psn.info.branch) {
                psn.ori_dom_psn = dom_psn;
                psn.ori_dom_psn_space = dom_space;
            }
        }
    }
    return master_name_list;
}
function fillMasterScript(dom_psn, psn) { // 填写某个人的上下标
    let dom_psn_super_s = dom_psn.find('.super-script');
    let psn_super_s_is_empty = false;
    let dom_psn_sub_s = dom_psn.find('.sub-script');
    let psn_sub_s_is_empty = false;
    let avai_list = []; // 可用的名号
    let default_scr_len = 3;
    let max_scr_len = avai_list.length>0? 6-avai_list[0].length : default_scr_len;
    // 长子次子名号
    if (psn.child_idx)
        avai_list.push(psn.child_idx);
    // 妻妾排行名号
    if (psn.wife_idx)
        avai_list.push(psn.wife_idx);
    // 尊称名号
    default_scr_len = 5;
    max_scr_len = avai_list.length>0? 6-avai_list[0].length : default_scr_len;
    if (!empty(psn.title_list)) {
        for (let t of psn.title_list) {
            let txt = t.title;
            if (txt.length>0 && txt.length<=max_scr_len)
                avai_list.push(txt);
        }
    }
    // 爵位官位名号
    if (!empty(psn.social_position)) {
        let [nobility = "", official = ""] = [psn.social_position.nobility_title, psn.social_position.official_title];
        if (nobility.length>0 && nobility.length<=max_scr_len) {
            avai_list.push(nobility);
        } else if (official.length>0 && official.length<=max_scr_len) {
            avai_list.push(official);
        }
    }
    // 诨名
    max_scr_len = avai_list.length>0? 6-avai_list[0].length : default_scr_len;
    if (!empty(psn.nick_list)) {
        for (let n of psn.nick_list) {
            let txt = n.nick;
            if (txt.length>0 && txt.length<=max_scr_len)
                avai_list.push(txt);
        }
    }
    // 所有可用名号里前两位就是上标下标
    if (avai_list.length>0) {
        let txt = avai_list[0];
        dom_psn_super_s.text(txt);
        if (txt.length>2)
            dom_psn_super_s.addClass('char'+txt.length);
    }
    if (avai_list.length>1) {
        let txt = avai_list[1];
        dom_psn_sub_s.text(txt);
        if (txt.length>2)
            dom_psn_sub_s.addClass('char'+txt.length);
    } else {
        dom_psn_sub_s.text('');
    }
    // 如果上标下标都是空的，则隐藏这块空间
    if (avai_list.length==0)
        dom_psn.find('.script-box').remove();
}
function calChildrenOrder(psn) { // 为这个人计算其长子次子长女次女这些信息
    psn.all_child_list = [];
    if (!empty(psn.child_list)) {
        for (let name of psn.child_list)
            psn.all_child_list.push(name);
    }
    if (!empty(psn.wife_list)) {
        for (let wife_name of psn.wife_list) {
            let wife = psn_map[wife_name];
            if (!empty(wife.child_list)) {
                for (let name of wife.child_list)
                    psn.all_child_list.push(name);
            }
        }
    }
}
function fillMasterServant(dom_psn, psn) { // 填充这个主子名下的丫鬟小厮仆人
    if (empty(psn.servant_list)) {
        dom_psn.find('.servant-list').remove();
    } else {
        const line_cnt = 6;
        let dom_svt_list = dom_psn.find('.servant-list').eq(0);
        let dom_svt_tpl = dom_svt_list.find('.servant').clone();
        let should_list_cnt = Math.ceil(psn.servant_list.length/line_cnt);
        for (let i=1; i<should_list_cnt; ++i) {
            dom_svt_list.clone().insertAfter(dom_svt_list);
        }
        dom_svt_list = dom_psn.find('.servant-list');
        dom_svt_list.find('.servant').remove();
        for (let i=0; i<psn.servant_list.length; ++i) {
            let dom_svt = dom_svt_tpl.clone();
            let svt = psn_map[ psn.servant_list[i] ];
            dom_svt.text(psn.servant_list[i]).attr('person',svt.name);
            if (svt.info.gender=='男')
                dom_svt.addClass('male');
            let list_idx = Math.floor(i/line_cnt);
            dom_svt_list.eq(list_idx).append(dom_svt);
        }
    }
}
function adjustPositionForAllMaster(family, master_name_list) { // 调整所有人物的位置（通过调整人物dom左侧的空白div的宽度）
    for (let n=0; n<100; ++n) {
        // 100是防止死循环，即最多进行这么多次的调整
        // 每次都是遍历所有人物，对人物位置进行检查。
        // 如果需要调整，则调整。本次调整结束，并进行下一次调整。
        // 如果遍历所有人物过程中没有发生任何调整，则认为全部调整完毕，函数结束。
        for (let name of master_name_list) {
            if (psn_map[name].all_child_list.length==0)
                continue;
            if (adjustPositionForOneMaster(family, name)>0)
                break;
        }
    }
}
let g_adjust_stop = 0;
function adjustPositionForOneMaster(family, name) { // 检查该人物跟其孩子的位置，并进行调整
    // 返回值： 0无任何调整，1调整了本人位置，2调整了孩子的位置
    if (g_adjust_stop)return 0;
    let psn = psn_map[name];
    let family_child_list = psn.all_child_list.filter(cname => psn_map[cname].info.family+'@'+psn_map[cname].info.branch==family.name);
    if (family_child_list.length==0)
        return 0;
    let left = psn.dom_psn.position().left;
    let width = psn.dom_psn.width();
    let right = left+width;
    let first_child = psn_map[ family_child_list[0] ];
    let first_child_dom = family.name==first_child.info.family+'@'+first_child.info.branch? first_child.dom_psn : first_child.ori_dom_psn;
    let last_child = psn_map[ family_child_list[family_child_list.length-1] ];
    let last_child_dom = family.name==last_child.info.family+'@'+last_child.info.branch? last_child.dom_psn : last_child.ori_dom_psn;
    let child_left_min = first_child_dom.position().left;
    let child_right_max = last_child_dom.position().left+last_child_dom.width();
    let child_width = child_right_max - child_left_min;
    if (left < child_left_min) {
        // 如果当前元素在首个孩子元素左侧，则自己前面要补充空白
        let dont_need = 0; // 但有种特殊情况是不需要调整位置的（比如：林如海和贾敏夫妇）
        if (empty(psn.child_list) // 没有直接连线孩子
            && family_child_list.length==1 // 总共只有一个孩子
            && left>first_child.dom_psn_space.position().left //当前元素处于首个孩子元素左侧的空白的上方
        ) {
            dont_need = 1;
        }
        if (dont_need)
            return 0;
        if (width - child_width >= 0.1
            && family_child_list.length==1) {
            // 先什么都不做吧！目前还没发现问题。
            // 如果当前元素比孩子元素要宽，且只有一个孩子，则把孩子元素中线对齐当前元素即可
            //let delta = left+width/2-child_width/2-child_left_min;
            //last_child_dom_space.width( last_child_dom_space.width()+delta );
            //console.log(`${name} 在末尾孩子 ${last_child.name} 右侧，为使中线对齐，末尾孩子左边补充空白：${right-child_right_max}`);
            //console.log('HAHAH');
            //console.log(`${name} 在首个孩子 ${first_child.name} 左侧，为使左边线对齐，自己左边补充空白：${child_left_min-left}`);
            //g_adjust_stop=1;
            return 0;
        } else {
            psn.dom_psn_space.width( psn.dom_psn_space.width()+(child_left_min-left) );
            //console.log(`${name} 在首个孩子 ${first_child.name} 左侧，为使左边线对齐，自己左边补充空白：${child_left_min-left}`);
            return 1;
        }
    } else if (right > child_right_max) {
        // 如果当前元素在末尾孩子元素右侧，则末尾孩子元素前面要补充空白
        let last_child_dom_space = psn_map[ family_child_list[family_child_list.length-1] ].dom_psn_space;
        if (width - child_width >= 0.1
            && family_child_list.length==1) {
            // 如果当前元素比孩子元素要宽，且只有一个孩子，则把孩子元素中线对齐当前元素即可
            let delta = left+width/2-child_width/2-child_left_min;
            last_child_dom_space.width( last_child_dom_space.width()+delta );
            //console.log(`${name} 在末尾孩子 ${last_child.name} 右侧，为使中线对齐，末尾孩子左边补充空白：${right-child_right_max}`);
        } else {
            // 否则，末尾孩子元素右边线对齐当前元素右边线
            last_child_dom_space.width( last_child_dom_space.width()+(right-child_right_max) );
            //console.log(`${name} 在末尾孩子 ${last_child.name} 右侧，为使右边线对齐，末尾孩子左边补充空白：${right-child_right_max}`);
        }
        return 2;
    }
    return 0;
}
function drawLevelLines(family, master_name_list) { // 为代际之间的关联关系画线
    for (let name of master_name_list) {
        let psn = psn_map[name];
        if (empty(psn.child_list))
            continue;
        if (psn.info.family+'@'+psn.info.branch!=family.name)
            continue;
        let dom_psn = psn.dom_psn;
        let psn_pos = dom_psn.position();
        let psn_w = dom_psn.outerWidth();
        let psn_h = dom_psn.outerHeight();
        let start_point = [psn_pos.left+psn_w/2, psn_pos.top+psn_h-dom_psn_tpl.outlineOffset];
        let end_point_list = [];
        for (let child_name of psn.child_list) {
            let child = psn_map[child_name];
            let dom_cpsn = family.name==child.info.family+'@'+child.info.branch? child.dom_psn : child.ori_dom_psn;
            if (typeof(dom_cpsn)=='undefined') console.log(name, child.name)
            let cpsn_pos = dom_cpsn.position();
            let cpsn_w = dom_cpsn.outerWidth(true);
            let cpsn_h = dom_cpsn.outerHeight(true);
            end_point_list.push([cpsn_pos.left+cpsn_w/2, cpsn_pos.top+dom_psn_tpl.outlineOffset]);
        }
        drawBranchLine(start_point, end_point_list);
    }
}
function drawBranchLine(start, end_list) { // 画分叉线
    let min_end_x = 999999;
    let max_end_x = -1;
    let min_end_y = 999999; // 结束点里纵坐标最小值
    for (let p of end_list) {
        if (p[0] < min_end_x)
            min_end_x = p[0];
        if (p[0] > max_end_x)
            max_end_x = p[0];
        if (p[1] < min_end_y)
            min_end_y = p[1];
    }
    if (start[0] < min_end_x)
        min_end_x = start[0];
    if (start[0] > max_end_x)
        max_end_x = start[0];
    let turn_y = (start[1]+min_end_y)/2; // 分叉点的纵坐标
    drawVLine(start, [start[0],turn_y]);
    drawHLine([min_end_x,turn_y], [max_end_x,turn_y]);
    for (let p of end_list) {
        drawVLine([p[0],turn_y], p);
    }
}
function drawVLine(start, end) { // 画一根竖线
    let l = vl_tpl.clone();
    l.css('left', start[0]).css('top', start[1]);
    l.css('height', end[1]-start[1]);
    dom_graph.append(l);
}
function drawHLine(start, end) { // 画一根横线
    let l = hl_tpl.clone();
    l.css('left', start[0]).css('top', start[1]);
    l.css('width', end[0]-start[0]);
    dom_graph.append(l);
}
function drawCoupleBoxes(family, master_name_list) { // 画夫妻框
    for (let name of master_name_list) {
        let psn = psn_map[name];
        if (psn.info.gender!='男' || empty(psn.wife_list))
            continue;
        let dom_psn = psn.dom_psn;
        let psn_pos = dom_psn.position();
        //let psn_w = dom_psn.outerWidth(true);
        let psn_h = dom_psn.outerHeight(true);
        let last_wife_name = psn.wife_list[psn.wife_list.length-1];
        let last_wife = psn_map[last_wife_name];
        let dom_wife = family.name==last_wife.info.family+'@'+last_wife.info.branch? last_wife.dom_psn : last_wife.ori_dom_psn;
        let wife_pos = dom_wife.position();
        let border_w = parseInt(dom_psn.css('outlineWidth'))+parseInt(dom_psn.css('borderTopWidth'));
        let dom_cpl_box = dom_cpl_tpl.clone();
        dom_cpl_box.css('left', psn_pos.left-border_w)
            .css('top', psn_pos.top-border_w);
        dom_cpl_box.css('width', wife_pos.left-psn_pos.left+dom_wife.outerWidth()+border_w)
            .css('height',psn_h+border_w);
        dom_cpl_box.attr('husband', name).attr('wife', last_wife_name);
        dom_graph.append(dom_cpl_box);
    }
}
function drawFamilyBorder(family) { // 一个家族绘制完毕后，绘制家族边界，防止其他家族的人物进入到本家族范围内
    let dom_list = [];
    let max_left = 0;
    for (let i=1; i<=5; ++i) {
        let dom_lvl = dom_graph.find(`[name=level-${i}]`);
        let dom_spc = $('<div class=space-between-person></div>');
        dom_spc.css('width','0px');
        dom_lvl.append(dom_spc);
        dom_list.push(dom_spc);
        let left = parseInt(dom_spc.position().left);
        if (left>max_left)
            max_left = left;
    }
    for (let dom_spc of dom_list) {
        dom_spc.css('width', max_left-parseInt(dom_spc.position().left));
    }
}

function onClickPerson(o) {
    let name = $(o).parents('[name=person]').attr('person');
    showPopper(name);
}
function onClickServant(o) {
    let name = $(o).text();
    showPopper(name);
}
function closePopper() {
    $('#popper').hide();
}
function empty(o) {
    return o==null || o==undefined || o==0 || o.length==0;
}
function showPopper(name) {
    let psn = psn_map[name];
    let popper = $('#popper').show();
    popper.find('tr[name=more]').remove();
    let tr_list = popper.find('tr').hide();
    tr_list.filter('.name').show().find('td').text(name);
    if (!empty(psn.ext)) {
        if (!empty(psn.ext.family_name)) {
            tr_list.filter('.family_name').show().find('td:nth-child(2)').text(psn.ext.family_name);
        }
        if (!empty(psn.ext.old_name)) {
            tr_list.filter('.old_name').show().find('td:nth-child(2)').text(psn.ext.old_name);
        }
        if (!empty(psn.ext.age) && !empty(psn.ext.age_note)) {
            tr_list.filter('.age').show().find('td:nth-child(2)').text(psn.ext.age+'岁 '+psn.ext.age_note);
        }
    }
    if (!empty(psn.action)) {
        let t = '';
        if (!empty(psn.action.ref_chap) && !empty(psn.action.ref_note)) {
            if (psn.action.ref_chap!=psn.action.chap) {
                t = '第'+psn.action.ref_chap+'回 '+psn.action.ref_note;
            }
        }
        if (empty(t)) {
            if (!empty(psn.action.chap) && !empty(psn.action.note)) {
                t = '第'+psn.action.ref_chap+'回 '+psn.action.ref_note;
                t = '第'+psn.action.chap+'回 '+psn.action.note;
            }
        }
        if (!empty(t))
            tr_list.filter('.action').show().find('td:nth-child(2)').text(t);
    }
    if (!empty(psn.title_list)) {
        let dom_title = tr_list.filter('.title').show();
        for (let i=0; i<psn.title_list.length; ++i) {
            let title = psn.title_list[i];
            if (i==0) {
                dom_title.find('td:nth-child(2)').text(title.title+' '+title.note);
            } else {
                let dn = dom_title.clone().attr('name', 'more');
                dn.find('td:nth-child(1)').text('');
                dn.find('td:nth-child(2)').text(title.title+' '+title.note);
                dom_title.after(dn);
            }
        }
    }
    if (!empty(psn.nick_list)) {
        let dom_nick = tr_list.filter('.nick').show();
        for (let i=0; i<psn.nick_list.length; ++i) {
            let nick = psn.nick_list[i];
            if (i==0) {
                dom_nick.find('td:nth-child(2)').text(nick.nick+' '+nick.note);
            } else {
                let dn = dom_nick.clone().attr('name', 'more');
                dn.find('td:nth-child(1)').text('');
                dn.find('td:nth-child(2)').text(nick.nick+' '+nick.note);
                dom_nick.after(dn);
            }
        }
    }
    if (!empty(psn.social_position)) {
        if (!empty(psn.social_position.nobility_title)) {
            tr_list.filter('.nobility').show().find('td:nth-child(2)').text(psn.social_position.nobility_title+' '+psn.social_position.nobility_note);
        }
        if (!empty(psn.social_position.official_title)) {
            tr_list.filter('.official').show().find('td:nth-child(2)').text(psn.social_position.official_title+' '+psn.social_position.official_note);
        }
    }
}
function txt2html(t) {
    ;
}
</script>
</head>
<body lang=cn>
    <div class=gragh-brief>版本：20180304</div>
    <div class=hide id=template>
        <div class=person name=person>
            <div class=master onClick="onClickPerson(this)">
                <div class=name>贾宝玉</div>
                <div class=script-box>
                    <div class=super-script>上标</div>
                    <div class=sub-script>下标</div>
                </div>
            </div>
            <div class=servant-list>
                <div class=servant onClick="onClickServant(this)">袭人</div>
            </div>
        </div>
        <div class=couple-box></div>
        <div class=hline></div>
        <div class=vline></div>
    </div>
    <div class=gragh-load id=gragh-load>正在加载</div>
    <div class=graph id="graph">
        <div class=level name=level-1></div>
        <div class=space-between-level></div>
        <div class=level name=level-2></div>
        <div class=space-between-level></div>
        <div class=level name=level-3></div>
        <div class=space-between-level></div>
        <div class=level name=level-4></div>
        <div class=space-between-level></div>
        <div class=level name=level-5></div>
    </div>
    <div class=popper id=popper>
        <div class=close onClick="closePopper()">关闭</div>
        <table>
            <tr class=name><td colspan=2>名称</td></tr>
            <tr class=family_name><td>姓氏</td><td></td></tr>
            <tr class=old_name><td>原名</td><td></td></tr>
            <tr class=age><td>年龄</td><td></td></tr>
            <tr class=action><td>出场</td><td></td></tr>
            <tr class=title><td>尊称</td><td></td></tr>
            <tr class=nick><td>昵称</td><td></td></tr>
            <tr class=nobility><td>爵位</td><td></td></tr>
            <tr class=official><td>官职</td><td></td></tr>
        </table>
    </div>
</body>
</html>